//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <elasys_server.h>
#include "CBasicFile.h"
#include "CBasicDirectory.h"

const wchar_t wszIllegal[] = L"\"*\\<>/?:|";

#ifdef _GNUC
const UInt64 bias64 = 0x019db1ded53e8000LL;
//1970.01.01 - 1601.01.01
#else
const UInt64 bias64 = 0x019db1ded53e8000;
//1970.01.01 - 1601.01.01
#endif

#define MIN_TIME_VALUE    31553280  //From 1970.1.1 to 1980.1.1

#define MAX_BUFSIZE   (0x7FFFFFFF)
#define MAX_FILESIZE  (0x70000000)

#define FIXME Aura_printf("[FixMe]<%s> Not Implemented.\n", __FUNCTION__);

#define LockFile do {AuraCriticalSection_Enter(m_hFileLock);} while (0);
#define UnlockFile do {AuraCriticalSection_Leave(m_hFileLock);} while (0);

UInt32 CBasicFile::AddRef()
{
    UInt32 nRef = m_cRef.Increment();
    return (UInt32)nRef;
}

UInt32 CBasicFile::Release()
{
    UInt32 nRef = 0;
    CBasicDirectory *pParentDir = static_cast<CBasicDirectory *>(m_pParentDir);
    Aura_Handle hParentFileListLock = m_hParentFileListLock;

    AuraCriticalSection_Enter(hParentFileListLock);
    nRef = m_cRef.Decrement();
    if (0 == nRef) {
        delete this;
        pParentDir->Release();
    }
    AuraCriticalSection_Leave(hParentFileListLock);

    return nRef;
}

ECode CBasicFile::Aggregate(
    AggregateType type, PInterface pObj)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::GetDomain(
    IInterface **ppObj)
{
    return E_NOT_IMPLEMENTED;
}

PInterface CBasicFile::Probe(REIID riid)
{
    if (EIID_IInterface == riid) {
        return (IInterface *)(IAbsoluteIO *)this;
    }
    else if (EIID_IAbsoluteIO == riid) {
        return (IAbsoluteIO *)this;
    }
    else if (EIID_IBasicFile == riid) {
        return (IBasicFile *)this;
    }
    else if (EIID_IFileMonitor == riid) {
        return (IFileMonitor *)this;
    }
    else if (EIID_CLASS_INFO == riid) {
        return (IInterface *)&ECLSID_CKtfsFile;
    }
    else if (EIID_INTERFACE_INFO == riid) {
        return (IInterface *)&EIID_IBasicFile;
    }

    return NULL;
}

CBasicFile::CBasicFile(IBasicDirectory *pParentDir)
{
    m_pFileName = NULL;
    m_CreateTime = 0;
    m_LastAccessTime = 0;
    m_LastModifyTime = 0;
    m_FileHandle = INVALID_HANDLE_VALUE;

    m_pParentDir = pParentDir;

    m_hFileLock = NULL;
    m_hParentFileListLock = (static_cast<CBasicDirectory *>(pParentDir))->GetFileObjectListLock();
}

ECode CBasicFile::Initialize(WString esFileName, FileShareMode fileShareMode)
{
    Int32 createMode = 0; 
    Int32 accessMode = 0;
    Int32 shareMode = 0;
    DWORD fileAttribute = 0;
    ECode ec = NOERROR;

    ec = AuraCriticalSection_Create(&m_hFileLock);
    if (NOERROR != ec)
        goto E_FAIL_EXIT;

    m_pFileName = (wchar_t *)Aura_malloc(sizeof(wchar_t) * (wcslen(esFileName) + 1));
    if (NULL == m_pFileName) {
        ec = E_OUT_OF_MEMORY;
        goto E_FAIL_EXIT;
    }
    wcscpy(m_pFileName, esFileName);

    fileAttribute = GetFileAttributesW(m_pFileName);
    if (INVALID_FILE_ATTRIBUTES == fileAttribute)  {
        ec = ECODE_FROM_WIN32(GetLastError());
        goto E_FAIL_EXIT;
    }

    if (fileAttribute & FILE_ATTRIBUTE_READONLY)
        m_FileAccessMode = FileAccessMode_Read;
    else
        m_FileAccessMode = FileAccessMode_ReadWrite;

    m_FileShareMode = fileShareMode;

    createMode = OPEN_ALWAYS;
    accessMode = GENERIC_READ;
    shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
    fileAttribute = FILE_ATTRIBUTE_NORMAL;
    if (FileAccessMode_ReadWrite == m_FileAccessMode)
        accessMode |= GENERIC_WRITE;

    m_FileHandle = CreateFileW(m_pFileName, accessMode, 
                        shareMode, NULL, createMode, fileAttribute, NULL);
    if (INVALID_HANDLE_VALUE == m_FileHandle) {
        ec = ECODE_FROM_WIN32(GetLastError());
        goto E_FAIL_EXIT;   
    }

    return NOERROR;

E_FAIL_EXIT:
    if (NULL != m_pFileName) {
        Aura_free(m_pFileName); 
        m_pFileName = NULL;
    }
    if (NULL != m_hFileLock) {
        AuraMutex_Destroy(m_hFileLock);
        m_hFileLock = NULL;
    }
    return ec;
}

ECode CBasicFile::Initialize(WString esFileName, FileAccessMode fileAccessMode, FileShareMode fileShareMode)
{
    Int32 createMode = 0; 
    Int32 accessMode = 0;
    Int32 shareMode = 0;
    DWORD fileAttribute = 0;
    ECode ec = NOERROR;

    if (FileShareMode_Read != fileShareMode
        && FileShareMode_Write != fileShareMode
        && FileShareMode_Deny != fileShareMode) {
       assert(0);
    }

    ec = AuraCriticalSection_Create(&m_hFileLock);
    if (NOERROR != ec)
        goto E_FAIL_EXIT;

    m_pFileName = (wchar_t *)Aura_malloc(sizeof(wchar_t) * (wcslen(esFileName) + 1));
    if (NULL == m_pFileName) {
        ec = E_OUT_OF_MEMORY;
        goto E_FAIL_EXIT;
    }
    wcscpy(m_pFileName, esFileName);

    createMode = OPEN_ALWAYS;
    accessMode = GENERIC_READ;
    shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
    fileAttribute = FILE_ATTRIBUTE_NORMAL;
    if (FileAccessMode_Write == fileAccessMode
       || FileAccessMode_ReadWrite == fileAccessMode)
        accessMode |= GENERIC_WRITE;

    m_FileHandle = CreateFileW(m_pFileName, accessMode, 
                        shareMode, NULL, createMode, fileAttribute, NULL);
    if (INVALID_HANDLE_VALUE == m_FileHandle) {
        ec = ECODE_FROM_WIN32(GetLastError());
        goto E_FAIL_EXIT;   
    }

    fileAttribute = GetFileAttributesW(m_pFileName);
    if (INVALID_FILE_ATTRIBUTES == fileAttribute)  {
        ec = ECODE_FROM_WIN32(GetLastError());
        goto E_FAIL_EXIT;
    }
    if (fileAttribute & FILE_ATTRIBUTE_READONLY)
        m_FileAccessMode = FileAccessMode_Read;
    else
        m_FileAccessMode = FileAccessMode_ReadWrite;

    m_FileAccessMode = fileAccessMode;
    m_FileShareMode = fileShareMode;
    return NOERROR;

E_FAIL_EXIT:
    if (NULL != m_pFileName) {
        Aura_free(m_pFileName); 
        m_pFileName = NULL;
    }
    if (NULL != m_hFileLock) {
        AuraMutex_Destroy(m_hFileLock);
        m_hFileLock = NULL;
    }
    return ec;
}

CBasicFile::~CBasicFile()
{
    this->Detach();

    if (NULL != m_pFileName) {
        Aura_free(m_pFileName);
    }
    if (INVALID_HANDLE_VALUE != m_FileHandle) {
        CloseHandle(m_FileHandle);
    }
    AuraCriticalSection_Destroy(m_hFileLock);
}

ECode CBasicFile::Read(
    /* [in] */ Int32 offset,
    /* [in] */ Int32 toRead,
    /* [out] */ MemoryBuf * pBuffer)
{
    UInt32 sizeToRead = 0;
    void *pBuf = NULL;
    Int32 fileSize = 0;
    UInt32 iRet = 0;
    BOOL bRet = FALSE;
    ECode ec = NOERROR;

    if (NULL == pBuffer || toRead < 0 ||offset < 0 ||  toRead > pBuffer->GetCapacity()) {
         ec = E_INVALID_ARGUMENT;
         goto EXIT;
    }

    LockFile;
    fileSize = GetFileSize(m_FileHandle, NULL);
    if (offset > fileSize || toRead > MAX_BUFSIZE) {
        ec = E_INVALID_ARGUMENT;
        goto EXIT;
    }

    iRet = SetFilePointer(m_FileHandle, offset, NULL, FILE_BEGIN);
    if (INVALID_SET_FILE_POINTER == iRet) {
        ec = E_INVALID_POINTER;
        goto EXIT;
    }

    pBuf = (char *)pBuffer->GetPayload();
    bRet = ReadFile(m_FileHandle, pBuf, toRead, &sizeToRead, NULL);
    if (!bRet) {
        ec = E_INVALID_OPERATION;
        goto EXIT;
    }
    pBuffer->SetUsed(sizeToRead);

EXIT:
    UnlockFile;
    return ec;
}

ECode CBasicFile::Write(
    /* [in] */ Int32 offset,
    /* [in] */ const MemoryBuf & ezbBuf,
    /* [out] */ Int32 * pWrite)
{
    ECode ec = NOERROR;
    Int32 bufSize = 0;
    Int32 fileSize = 0;
    DWORD writenSize = 0;
    DWORD ret = 0;
    BOOL bSucceed = FALSE;

    if (NULL == pWrite || ezbBuf.IsNullOrEmpty()) {
        return E_INVALID_ARGUMENT;
    }

    bufSize = ezbBuf.GetUsed();
    if (bufSize > MAX_BUFSIZE || offset + bufSize > MAX_FILESIZE) {
        return E_INVALID_ARGUMENT;
    }

    LockFile;
    if (FileAccessMode_Read == m_FileAccessMode) {
        ec = E_FS_NO_PERMISSION;
        goto EXIT;
    }

    fileSize = GetFileSize(m_FileHandle, NULL);
    if ((offset > fileSize) || (offset + bufSize) > fileSize) {
        //ec =  E_INVALID_OPERATION;
        ret = SetFilePointer(m_FileHandle, offset + bufSize, NULL, FILE_BEGIN);
        if (-1 == ret) {
            ec = ECODE_FROM_WIN32(GetLastError());;
            goto EXIT;
        }
        bSucceed = SetEndOfFile(m_FileHandle);
        if (!bSucceed) {
            ec = ECODE_FROM_WIN32(GetLastError());
            goto EXIT;
        }
    }

    ret = SetFilePointer(m_FileHandle, offset, NULL, FILE_BEGIN);
    if (INVALID_SET_FILE_POINTER == ret) {
        ec = E_INVALID_POINTER;
        goto EXIT;
    }

    bSucceed = WriteFile(m_FileHandle, ezbBuf.GetPayload(), bufSize, &writenSize, NULL);
    if (!bSucceed) {
        ec = ECODE_FROM_WIN32(GetLastError());
        goto EXIT;
    }
    *pWrite = writenSize;

EXIT:
    UnlockFile;
    return ec;
}

ECode CBasicFile::GetSize(
    /* [out] */ Int32 * pSize)
{
    if (NULL == pSize) {
        return E_INVALID_ARGUMENT;
    }

    LockFile;
    *pSize = GetFileSize(m_FileHandle, NULL);
    UnlockFile;

    return NOERROR;
}

ECode CBasicFile::SetSize(
    /* [in] */ Int32 size)
{
    ECode ec = NOERROR;
    UInt32 position = 0;
    BOOL ret = FALSE;

    LockFile;
    if (FileAccessMode_Read == m_FileAccessMode) {
        ec = E_FS_NO_PERMISSION;
        goto EXIT;
    }

    position = SetFilePointer(m_FileHandle, size, NULL, FILE_BEGIN);
    if (-1 == position) {
        ec = E_INVALID_OPERATION;
        goto EXIT;
    }

    ret = SetEndOfFile(m_FileHandle);
    if (!ret) {
        ec = E_INVALID_OPERATION;
    }

EXIT:
    UnlockFile;
    return ec;
}

ECode CBasicFile::Sync()
{
    return NOERROR;
}

ECode CBasicFile::GetType(
    /* [out] */ FileType * pType)
{
    if (NULL == pType) {
        return E_INVALID_ARGUMENT;
    }

    *pType = FileType_RegularFile;
    return NOERROR;
}

ECode CBasicFile::GetAccessTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec = NOERROR;

    if (NULL == pTime)
        return E_INVALID_ARGUMENT;

    if (0 == m_LastAccessTime) {
        ec = GetFileTime();
        if (NOERROR != ec)
            return ec;
    }
    *pTime = m_LastAccessTime;
    return ec;
}

ECode CBasicFile::SetAccessTime(
    /* [in] */ Int32 time)
{
    FIXME;
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::GetModificationTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec = NOERROR;
    if (NULL == pTime) {
        return E_INVALID_ARGUMENT;
    }

    LockFile;
    if (0 == m_LastModifyTime) {
        ec = GetFileTime();
        if (NOERROR != ec)
            return ec;
    }
    *pTime = m_LastModifyTime;
    UnlockFile;

    return NOERROR;
}

ECode CBasicFile::SetModificationTime(
    /* [in] */ Int32 time)
{
    BOOL bRet = FALSE;

    if (time < MIN_TIME_VALUE) {
        return E_INVALID_ARGUMENT;
    }

    LockFile;
    if (FileAccessMode_Read == m_FileAccessMode) {
        UnlockFile;
        return E_FS_NO_PERMISSION;
    }

    FILETIME ft;
    LocalTimeToFileTime(time, &ft);
    bRet = SetFileTime(m_FileHandle, NULL, NULL, &ft);
    if (!bRet) {
        UnlockFile;
        return ECODE_FROM_WIN32(GetLastError());
    }
    m_LastModifyTime = time;
    UnlockFile;
    return NOERROR;
}

ECode CBasicFile::GetCreateTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec = NOERROR;

    if (NULL == pTime) {
        return E_INVALID_ARGUMENT;
    }

    if (0 == m_LastAccessTime) {
        ec = GetFileTime();
        if (NOERROR != ec) {
            return ec;
        }
    }

    *pTime = m_CreateTime;
    return ec;
}

ECode CBasicFile::CheckAccessMode(
    /* [in] */ FileAccessMode mode)
{
    FIXME;
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::GetAccessMode(
    /* [out] */ FileAccessMode * pMode)
{
    if (NULL == pMode) {
        return E_INVALID_ARGUMENT;
    }

    LockFile;

    if (FileAccessMode_Write == m_FileAccessMode)
        *pMode = FileAccessMode_ReadWrite;
    else
        *pMode = m_FileAccessMode;

    UnlockFile;

    return NOERROR;
}

ECode CBasicFile::SetAccessMode(
    /* [in] */ FileAccessMode mode)
{
    DWORD attr = 0;
    ECode ec = 0;
    BOOL bSucceed = FALSE;

    if ((FileAccessMode_Read != mode)
        && (FileAccessMode_Write != mode)
        && (FileAccessMode_ReadWrite != mode)) {
        return E_INVALID_ARGUMENT;
    }

    LockFile;

    if (mode != m_FileAccessMode) {
        attr = GetFileAttributesW(m_pFileName);
        if (INVALID_FILE_ATTRIBUTES == attr)
            goto EXIT;

        if (FileAccessMode_Read == mode)
            attr |= FILE_ATTRIBUTE_READONLY;        
        else
            attr &= ~FILE_ATTRIBUTE_READONLY;

        bSucceed = SetFileAttributesW(m_pFileName, attr);
        if (!bSucceed)
             goto EXIT;

        m_FileAccessMode = mode;
    }

    UnlockFile;
    return NOERROR;

EXIT:
    UnlockFile;
    ec = ECODE_FROM_WIN32(GetLastError());
    return ec;
}

ECode CBasicFile::GetShareMode(
    /* [out] */ FileShareMode * pMode)
{
     FIXME;
     return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::SetShareMode(
    /* [in] */ FileShareMode mode)
{
    FIXME;
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::AddFileMonitor(
    /* [out] */ IEvent **pEvent)
{
    FIXME;
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::RemoveFileMonitor(
    /* [in] */ IEvent *pEvent)
{
    FIXME;
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile:: GetOptimalBlockSize(
    /* [out] */ Int32 * pSize)
{
    if (NULL == pSize) {
        return E_INVALID_ARGUMENT;
    }
    *pSize = 4096;
    return NOERROR;
}

wchar_t *CBasicFile::GetFileName(void) const
{
    return m_pFileName;
}

ECode CBasicFile::GetFileTime()
{
    BOOL bRet = FALSE;
    UInt64 tmp = 0;
    FILETIME createTime;
    FILETIME lastAccessTime;
    FILETIME lastModifyTime;

    assert(INVALID_HANDLE_VALUE != m_FileHandle);

    bRet = ::GetFileTime(m_FileHandle, &createTime, &lastAccessTime, &lastModifyTime);
    if (!bRet) {
        return ECODE_FROM_WIN32(GetLastError());
    }

    tmp = *(UInt64 *)&createTime;
    m_CreateTime = (UInt32)((tmp - bias64) / 10000000);
    tmp = *(UInt64 *)&lastAccessTime;
    m_LastAccessTime = (UInt32)((tmp - bias64) / 10000000);
    tmp = *(UInt64 *)&lastModifyTime;
    m_LastModifyTime = (UInt32)((tmp - bias64) / 10000000);

    return NOERROR;
}

void CBasicFile::LocalTimeToFileTime(Int32 time, FILETIME *pFt)
{
    assert(NULL != pFt);
    UInt64 tmp = (UInt64)(time) * 10000000 + bias64;
    pFt->dwLowDateTime = (UInt32)tmp;
    pFt->dwHighDateTime = (UInt32)(tmp >> 32);
}

